
/*
 * Copyright (c) 2010 Sergey N Frolov
 * 
 * See LICENSE file
 */

#include "ngx_javascript.h"

size_t ngx_javascript_fcgi_atos(unsigned char *line, size_t n) {
	size_t value;
	if (n == 0) return 0;
	for (value = 0; n --; line ++) {
		if (*line < '0' || *line > '9') return 0;
		value = value * 10 + (*line - '0');
	}
	return value;
}


/*
 * Read fastcgi header (8 bytes). Each packet should contain at least this size of data
 */
int ngx_javascript_fcgi_read_header(ngx_javascript_client_t *client, fcgi_header *hdr) {
	if (ngx_javascript_client_read(client, (char *) hdr, sizeof(fcgi_header)) != sizeof(fcgi_header) || hdr->version < FCGI_VERSION_1) {
# if 0
		fprintf(stderr, "FastCGI: cannot read header\n");
# endif
		return -1;
	}
	return sizeof(fcgi_header);
}

/*
 * Set status of request processing. If we expect some data, allocate memory for it
 */
int ngx_javascript_fcgi_next_status(ngx_javascript_client_t *client, int status, int len, int pad) {
	if (len + pad > FCGI_MAX_LENGTH) {
# if 0
		fprintf(stderr, "FastCGI: packet length too large\n");
# endif
		return -1;
	}
	if (client->input != NULL) {
		if (len + pad > client->input_dat) {
			ngx_javascript_free(client->input);
			client->input = NULL;
			client->input_dat = 0;
		}
	}
	client->status = status;
	client->input_len = len;
	client->input_pad = pad;
	client->input_pos = 0;
	if (len + pad > 0) {
		if (client->input == NULL) {
			client->input = ngx_javascript_malloc(len + pad > client->server->pagesize ? len + pad : client->server->pagesize);
			if (client->input == NULL) return -1;
			client->input_dat = len + pad;
		}
	}
	return 0;
}

/*
 * Read request params
 */
int ngx_javascript_fcgi_get_params(ngx_javascript_client_t *client) {
	unsigned char *data = ngx_javascript_client_allocate(client, client->input_pos);
	unsigned char *d = data;
	unsigned char *p = (unsigned char *) client->input;
	unsigned char *e = (unsigned char *) (client->input + client->input_pos);
	int key_len, val_len;
	unsigned char *k, *s;
	while (p < e) {
		key_len = *p++;
		if (p >= e) return 0;
		if (key_len >= 128) {
			if (p + 3 >= e) return 0;
			key_len = ((key_len & 0x7f) << 24);
			key_len |= (*p++ << 16);
			key_len |= (*p++ << 8);
			key_len |= *p++;
		}
		val_len = *p++;
		if (p >= e) return 0;
		if (val_len >= 128) {
			if (p + 3 >= e) return 0;
			val_len = ((val_len & 0x7f) << 24);
			val_len |= (*p++ << 16);
			val_len |= (*p++ << 8);
			val_len |= *p++;
		}
		if (key_len + val_len == 0) {
			break;
		} else if (key_len + val_len < 0 || key_len + val_len > e - p) {
			return -1;
		}
		s = p + key_len;
		memcpy(d, p, key_len); k = d; d += key_len; *d = '\0'; d ++;
		memcpy(d, s, val_len); s = d; d += val_len; *d = '\0'; d ++;
# if 0
		fprintf(stderr, "%s = %s\n", k, s);
# endif
		JS_HashTableAdd(client->env, k, s);
		p += key_len + val_len;
		if (key_len == sizeof("DOCUMENT_ROOT") - 1 && strncmp((char *) k, "DOCUMENT_ROOT", key_len) == 0) {
			client->document_root = (char *) s;
			client->document_root_len = val_len;
		} else if (key_len == sizeof("SCRIPT_FILENAME") - 1 && strncmp((char *) k, "SCRIPT_FILENAME", key_len) == 0) {
			while (val_len > 0 && *s == '/') {
				val_len --;
				s ++;
			}
			client->script_filename = (char *) s;
			client->script_filename_len = val_len;
		} else if (key_len == sizeof("OPEN_BASEDIR") - 1 && strncmp((char *) k, "OPEN_BASEDIR", key_len) == 0) {
			client->open_basedir = (char *) s;
			client->open_basedir_len = val_len;
		} else if (key_len == sizeof("REQUEST_URI") - 1 && strncmp((char *) k, "REQUEST_URI", key_len) == 0) {
			client->request_uri = (char *) s;
			client->request_uri_len = val_len;
		} else if (key_len == sizeof("QUERY_STRING") - 1 && strncmp((char *) k, "QUERY_STRING", key_len) == 0) {
			client->query_string = (char *) s;
			client->query_string_len = val_len;
		} else if (key_len == sizeof("HTTP_COOKIE") - 1 && strncmp((char *) k, "HTTP_COOKIE", key_len) == 0) {
			client->cookie = (char *) s;
			client->cookie_len = val_len;
		} else if (key_len == sizeof("HTTP_CONTENT_TYPE") - 1 && strncmp((char *) k, "HTTP_CONTENT_TYPE", key_len) == 0) {
			client->content_type = (char *) s;
			client->content_type_len = val_len;
		} else if (key_len == sizeof("HTTP_CONTENT_LENGTH") - 1 && strncmp((char *) k, "HTTP_CONTENT_LENGTH", key_len) == 0) {
			client->content_length = ngx_javascript_fcgi_atos(s, val_len);
		}
	}
	return 0;
}

/*
 * Read request. Called after each read event. 1 - continue, 0 - stop and execute script, -1 - error
 */
int ngx_javascript_fcgi_read_request(ngx_javascript_client_t *client) {
	fcgi_header hdr;
	int len, pad;
	if (client->input_len + client->input_pad > client->input_pos) {
		int r = ngx_javascript_client_read(client, client->input + client->input_pos, client->input_len + client->input_pad - client->input_pos);
		if (r > 0) client->input_pos += r;
		return r;
	}
	if (client->status == NGX_JAVASCRIPT_STATUS_READ) {
# if 0
		fprintf(stderr, "NGX_JAVASCRIPT_STATUS_READ\n");
# endif
		if (ngx_javascript_fcgi_read_header(client, &hdr) < 0) return -1;
		len = (hdr.contentLengthB1 << 8) | hdr.contentLengthB0;
		pad = hdr.paddingLength;
		client->id = (hdr.requestIdB1 << 8) + hdr.requestIdB0;
		if (hdr.type == FCGI_BEGIN_REQUEST) {
			if (len == sizeof(fcgi_begin_request)) {
				if (ngx_javascript_fcgi_next_status(client, NGX_JAVASCRIPT_STATUS_BEGIN_REQUEST, len, pad) < 0) return -1;
			} else {
# if 0
				fprintf(stderr, "FastCGI: wrong begin request length\n");
# endif
				return -1;
			}
		} else if (hdr.type == FCGI_ABORT_REQUEST) {
			if (ngx_javascript_fcgi_next_status(client, NGX_JAVASCRIPT_STATUS_ABORT_REQUEST, len, pad) < 0) return -1;
			return -1;
		} else if (hdr.type == FCGI_GET_VALUES) {
			if (ngx_javascript_fcgi_next_status(client, NGX_JAVASCRIPT_STATUS_GET_VALUES, len, pad) < 0) return -1;
		} else if (hdr.type == FCGI_PARAMS) {
			if (ngx_javascript_fcgi_next_status(client, NGX_JAVASCRIPT_STATUS_PARAMS, len, pad) < 0) return -1;
		} else if (hdr.type == FCGI_STDIN) {
			if (len == 0) {
				return 0;
			} else {
				if (ngx_javascript_fcgi_next_status(client, NGX_JAVASCRIPT_STATUS_STDIN, len, pad) < 0) return -1;
			}
		} else if (hdr.type == FCGI_DATA) {
			if (ngx_javascript_fcgi_next_status(client, NGX_JAVASCRIPT_STATUS_DATA, len, pad) < 0) return -1;
			return -1;
		} else {
# if 0
			fprintf(stderr, "FastCGI: wrong header type\n");
# endif
			return -1;
		}
	} else if (client->status == NGX_JAVASCRIPT_STATUS_BEGIN_REQUEST) {
# if 0
		fprintf(stderr, "NGX_JAVASCRIPT_STATUS_BEGIN_REQUEST\n");
# endif
		char *buf = client->input;
		client->keep = (((fcgi_begin_request*) buf)->flags & 1);
		switch ((((fcgi_begin_request*)buf)->roleB1 << 8) + ((fcgi_begin_request*)buf)->roleB0) {
			case FCGI_RESPONDER:
# if 0
				fprintf(stderr, "RESPONDER\n");
# endif
				break;
			case FCGI_AUTHORIZER:
# if 0
				fprintf(stderr, "AUTHORIZER\n");
# endif
				return -1;
				break;
			case FCGI_FILTER:
# if 0
				fprintf(stderr, "FILTER\n");
# endif
				return -1;
				break;
			default:
# if 0
				fprintf(stderr, "FastCGI: wrong request role\n");
# endif
				return -1;
		}
		if (ngx_javascript_fcgi_next_status(client, NGX_JAVASCRIPT_STATUS_READ, 0, 0) < 0) return -1;
	} else if (client->status == NGX_JAVASCRIPT_STATUS_GET_VALUES) {
# if 0
		fprintf(stderr, "NGX_JAVASCRIPT_STATUS_VALUES\n");
# endif
		if (client->input_pos > 0) if (ngx_javascript_fcgi_get_params(client) < 0) return -1;
		ngx_javascript_fcgi_next_status(client, NGX_JAVASCRIPT_STATUS_READ, 0, 0);
	} else if (client->status == NGX_JAVASCRIPT_STATUS_PARAMS) {
# if 0
		fprintf(stderr, "NGX_JAVASCRIPT_STATUS_PARAMS\n");
# endif
		if (client->input_pos > 0) if (ngx_javascript_fcgi_get_params(client) < 0) return -1;
		ngx_javascript_fcgi_next_status(client, NGX_JAVASCRIPT_STATUS_READ, 0, 0);
		if (client->content_type && client->content_length > 0) {
			if (client->body != NULL) {
				ngx_javascript_free(client->body);
				client->body = NULL;
			}
			if (client->body_fd >= 0) {
				ngx_javascript_client_body(client, 0);
			}
			if (client->content_type_len >= sizeof("application/x-www-form-urlencoded") - 1 && strncmp(client->content_type, "application/x-www-form-urlencoded", sizeof("application/x-www-form-urlencoded") - 1) == 0) {
				if (client->content_length < client->server->max_form) {
					client->body = ngx_javascript_malloc(client->content_length + 1);
					if (client->body != NULL) {
						client->body[client->content_length] = '\0';
						client->body_type = NGX_JAVASCRIPT_PARSE_FORM;
					}
				}
			} else if (client->content_type_len >= sizeof("multipart/form-data") - 1 && strncmp(client->content_type, "multipart/form-data", sizeof("multipart/form-data") - 1) == 0) {
				if (client->content_length < client->server->max_form) {
					client->body = ngx_javascript_malloc(client->content_length + 1);
					if (client->body != NULL) {
						client->body[client->content_length] = '\0';
						client->body_type = NGX_JAVASCRIPT_PARSE_DATA;
					}
				} else if (client->content_length < client->server->max_data) {
					ngx_javascript_client_body(client, 1);
					client->body_type = NGX_JAVASCRIPT_PARSE_DATA;
				}
			}
			client->body_pos = 0;
		}
	} else if (client->status == NGX_JAVASCRIPT_STATUS_STDIN) {
# if 0
		fprintf(stderr, "NGX_JAVASCRIPT_STATUS_STDIN\n");
# endif
		if (client->input_pos == 0) return 0;
		if (client->body_fd >= 0) {
			client->body_pos += write(client->body_fd, client->input, client->input_len);
		} else if (client->body != NULL) {
			if (client->body_pos + client->input_len <= client->content_length) {
				memcpy(client->body + client->body_pos, client->input, client->input_len);
				client->body_pos += client->input_len;
			} else if (client->body_pos < client->content_length) {
				memcpy(client->body + client->body_pos, client->input, client->content_length - client->body_pos);
				client->body_pos = client->content_length;
			}
		}
		ngx_javascript_fcgi_next_status(client, NGX_JAVASCRIPT_STATUS_READ, 0, 0);
	} else {
# if 0
		fprintf(stderr, "NGX_JAVASCRIPT_STATUS_NONE\n");
# endif
		return -1;
	}
	return 1;
}

int ngx_javascript_fcgi_padding(int len) {
	return ((len + 7) & ~7) - len;
}

int ngx_javascript_fcgi_make_header(fcgi_header *hdr, fcgi_request_type type, int req_id, int len, int pad) {
	hdr->contentLengthB0 = (unsigned char)(len & 0xff);
	hdr->contentLengthB1 = (unsigned char)((len >> 8) & 0xff);
	hdr->paddingLength = (unsigned char)pad;
	hdr->requestIdB0 = (unsigned char)(req_id & 0xff);
	hdr->requestIdB1 = (unsigned char)((req_id >> 8) & 0xff);
	hdr->reserved = 0;
	hdr->type = type;
	hdr->version = FCGI_VERSION_1;
	return 0;
}

int ngx_javascript_fcgi_write_responce(ngx_javascript_client_t *client) {
	ngx_javascript_buffer_t *buffer = client->output;
	char *p;
	int pos = client->position;
	int r;
	if (buffer == NULL) {
		char b[16];
		memset(b, 0, sizeof(b));
		p = b;
		fcgi_end_request_rec *rec = (fcgi_end_request_rec*) b;
		ngx_javascript_fcgi_make_header(&rec->hdr, FCGI_END_REQUEST, client->id, sizeof(fcgi_end_request), 0);
		rec->body.appStatusB3 = 0;
		rec->body.appStatusB2 = 0;
		rec->body.appStatusB1 = 0;
		rec->body.appStatusB0 = 0;
		rec->body.protocolStatus = FCGI_REQUEST_COMPLETE;
		r = ngx_javascript_client_write(client, p, sizeof(fcgi_end_request_rec));
		return 0;
	}
	p = buffer->data + pos - sizeof(fcgi_header);
	r = ngx_javascript_client_write(client, p, buffer->len - pos);
	if (r < 0) return r;
	client->position += r;
	if (client->position < buffer->len) {
		client->position += r;
	} else {
		client->output = buffer->next;
		client->position = 0;
		ngx_javascript_free(buffer);
	}
	return r;
}
